package com.hqd.ch03.v28.spel.parse;

import com.hqd.ch03.v28.spel.Expression;
import com.hqd.ch03.v28.spel.ParserContext;
import com.hqd.ch03.v28.spel.ast.*;
import com.hqd.ch03.v28.spel.express.CompoundExpression;
import com.hqd.ch03.v28.spel.standard.SpelExpression;
import com.hqd.ch03.v28.spel.standard.Token;
import com.hqd.ch03.v28.spel.standard.TokenKind;
import com.hqd.ch03.v28.spel.standard.Tokenizer;
import org.apache.commons.lang3.ArrayUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * spel表达式解析
 */
public class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
    private String expressionString = "";
    private int pos = 0;
    private List<Token> tokenStream;

    @Override
    protected Expression doParseExpression(String expressionString, ParserContext context) {
        this.expressionString = expressionString;
        Tokenizer tokenizer = new Tokenizer(expressionString);
        tokenStream = tokenizer.process();
        this.pos = 0;
        SpelNodeImpl ast = eatExpression();
        return new SpelExpression(expressionString, ast);
    }

    /**
     * 运算符优先级，乘除先，加减后
     *
     * @return
     */
    private SpelNodeImpl eatExpression() {
        return eatLogicalOrExpression();
    }

    /**
     * 生成or节点
     *
     * @return
     */
    private SpelNodeImpl eatLogicalOrExpression() {
        SpelNodeImpl expr = eatLogicalAndExpression();
        while (peekToken(TokenKind.SYMBOLIC_OR)) {
            Token token = nextToken();
            SpelNodeImpl rhExpr = eatLogicalAndExpression();
            expr = new OpOr(token.getStartPos(), token.getEndPos(), expr, rhExpr);
        }
        return expr;
    }

    /**
     * 生成and节点
     *
     * @return
     */
    private SpelNodeImpl eatLogicalAndExpression() {
        SpelNodeImpl expr = eatRelationalExpression();
        while (peekToken(TokenKind.SYMBOLIC_AND)) {
            Token token = nextToken();
            SpelNodeImpl rhExpr = eatRelationalExpression();
            expr = new OpAnd(token.getStartPos(), token.getEndPos(), expr, rhExpr);
        }
        return expr;
    }

    /**
     * 生成 <、>、!=、<=、>=等节点
     *
     * @return
     */
    private SpelNodeImpl eatRelationalExpression() {
        SpelNodeImpl expr = eatSumExpression();
        Token t = peekToken();
        if (t == null) {
            return expr;
        }
        if (t.isNumericRelationalOperator()) {
            t = nextToken();
            TokenKind tk = t.getKind();
            SpelNodeImpl rhExpr = eatSumExpression();
            if (tk == TokenKind.GT) {
                return new OpGt(t.getStartPos(), t.getEndPos(), expr, rhExpr);
            }
            if (tk == TokenKind.LT) {
                return new OpLt(t.getStartPos(), t.getEndPos(), expr, rhExpr);
            }
            if (tk == TokenKind.LE) {
                return new OpLe(t.getStartPos(), t.getEndPos(), expr, rhExpr);
            }
            if (tk == TokenKind.GE) {
                return new OpGe(t.getStartPos(), t.getEndPos(), expr, rhExpr);
            }
            if (tk == TokenKind.EQ) {
                return new OpEQ(t.getStartPos(), t.getEndPos(), expr, rhExpr);
            }
            if (tk == TokenKind.NE) {
                return new OpNE(t.getStartPos(), t.getEndPos(), expr, rhExpr);
            }
        }
        return expr;
    }

    /**
     * 生成+、-节点
     *
     * @return
     */
    private SpelNodeImpl eatSumExpression() {
        SpelNodeImpl expr = eatProductExpression();
        while (peekToken(TokenKind.MINUS, TokenKind.PLUS)) {
            Token token = nextToken();
            SpelNodeImpl rgNode = eatProductExpression();
            if (token.getKind() == TokenKind.MINUS) {
                expr = new OpMinus(token.getStartPos(), token.getEndPos(), expr, rgNode);
            } else if (token.getKind() == TokenKind.PLUS) {
                expr = new OpPlus(token.getStartPos(), token.getEndPos(), expr, rgNode);
            }
        }
        return expr;
    }

    /**
     * 生成*、/、%节点
     *
     * @return
     */
    private SpelNodeImpl eatProductExpression() {
        SpelNodeImpl expr = eatUnaryExpression();
        if (expr == null) {
            throw new RuntimeException("表达式左边必须为数字");
        }
        while (peekToken(TokenKind.DIV, TokenKind.STAR, TokenKind.MOD)) {
            Token token = nextToken();
            SpelNodeImpl rgLiteralNode = eatLiteralNode();
            if (token.getKind() == TokenKind.DIV) {
                expr = new OpDiv(token.getStartPos(), token.getEndPos(), expr, rgLiteralNode);
            } else if (token.getKind() == TokenKind.STAR) {
                expr = new OpStar(token.getStartPos(), token.getEndPos(), expr, rgLiteralNode);
            } else if (token.getKind() == TokenKind.MOD) {
                expr = new OpMod(token.getStartPos(), token.getEndPos(), expr, rgLiteralNode);
            }
        }
        return expr;
    }

    /**
     * 生成一元表达式节点
     *
     * @return
     */
    private SpelNodeImpl eatUnaryExpression() {
        if (peekToken(TokenKind.NOT)) {
            Token t = nextToken();
            SpelNodeImpl expr = eatUnaryExpression();
            if (t.getKind() == TokenKind.NOT) {
                return new OpNOT(t.getStartPos(), t.getEndPos(), expr);
            }
        }
        return eatLiteralNode();
    }

    /**
     * 生成常量、变量节点
     *
     * @return
     */
    private SpelNodeImpl eatLiteralNode() {
        Token token = nextToken();
        if (token.getKind() == TokenKind.LITERAL_INT) {
            return new IntLiteral(token.getStartPos(), token.getEndPos(), token.getData());
        } else if (token.getKind() == TokenKind.LPAREN) {
            SpelNodeImpl spelNode = eatExpression();
            token = nextToken();
            if (token != null && token.getKind() == TokenKind.RPAREN) {
                return spelNode;
            }
            throw new RuntimeException("没有匹配到')'");
        } else if (token.getKind() == TokenKind.LITERAL_REAL) {
            return new RealLiteral(token.getStartPos(), token.getEndPos(), token.getData());
        } else if (token.getKind() == TokenKind.LITERAL_STRING) {
            return new StringLiteral(token.getStartPos(), token.getEndPos(), token.getData());
        } else if (token.getKind() == TokenKind.IDENTIFIER) {
            return eatIdentifier(token);
        }
        return null;
    }

    /**
     * 生成变量节点
     *
     * @param token
     * @return
     */
    private SpelNodeImpl eatIdentifier(Token token) {
        /**
         * 有可能是方法
         */
        SpelNodeImpl[] args = maybeEatMethodArgs();
        SpelNodeImpl startNode;
        List<SpelNodeImpl> nodes = new ArrayList<>();
        if (args == null) {
            startNode = new PropertyOrFieldReference(token.getData(), token.getStartPos(), token.getEndPos());
        } else {
            startNode = new MethodReference(token.getData(), token.getStartPos(), token.getEndPos(), args);
        }
        nodes.add(startNode);
        eatMethodOrProperty(nodes, token);
        return new CompoundExpression(startNode.getStartPos(), nodes.get(nodes.size() - 1).getEndPos(),
                nodes.toArray(new SpelNodeImpl[0]));
    }

    /**
     * 生成方法节点
     *
     * @param nodes
     * @param token
     */
    private void eatMethodOrProperty(List<SpelNodeImpl> nodes, Token token) {
        if (peekToken(TokenKind.DOT)) {
            nextToken();
            if (peekToken(TokenKind.IDENTIFIER)) {
                token = nextToken();
            }
            SpelNodeImpl[] args = maybeEatMethodArgs();
            SpelNodeImpl node;
            if (args == null) {
                node = new PropertyOrFieldReference(token.getData(), token.getStartPos(), token.getEndPos());
            } else {
                node = new MethodReference(token.getData(), token.getStartPos(), token.getEndPos(), args);
            }
            nodes.add(node);
            eatMethodOrProperty(nodes, token);
        }
    }

    private SpelNodeImpl[] maybeEatMethodArgs() {
        if (!peekToken(TokenKind.LPAREN)) {
            return null;
        }
        nextToken();
        List<SpelNodeImpl> accumulatedArguments = new ArrayList<>();
        while (!peekToken(TokenKind.RPAREN)) {
            accumulatedArguments.add(eatExpression());
            if (!peekToken(TokenKind.COMMA)) {
                break;
            }
            nextToken();
        }
        if (!peekToken(TokenKind.RPAREN)) {
            throw new RuntimeException("方法无法匹配')'");
        }
        nextToken();
        return accumulatedArguments.toArray(new SpelNodeImpl[0]);
    }

    private boolean peekToken(TokenKind... possibles) {
        Token t = peekToken();
        if (t == null) {
            return false;
        }
        if (ArrayUtils.isEmpty(possibles)) {
            return false;
        }

        return ArrayUtils.contains(possibles, t.getKind());
    }

    private Token peekToken() {
        if (pos >= tokenStream.size()) {
            return null;
        }
        return tokenStream.get(pos);
    }

    private Token nextToken() {
        if (pos >= tokenStream.size()) {
            return null;
        }
        return tokenStream.get(pos++);
    }


}
